using System.Configuration;
using System.IO;
using System.Text.RegularExpressions;

namespace org.jawk.jrt
{
	/// <summary>
	/// A reader which consumes one record at a time from
	/// an underlying input reader.
	/// </summary>
	/// <remarks>
	/// A reader which consumes one record at a time from
	/// an underlying input reader.
	/// <p>
	/// <h3>Greedy Regex Matching</h3>
	/// The current implementation matches RS against
	/// contents of an input buffer (the underlying input
	/// stream filling the input buffer).  Records are
	/// split against the matched regular expression
	/// input, treating the regular expression as a
	/// record separator.
	/// <p>
	/// By default, greedy regular expression matching
	/// for RS is turned off.  It is assumed
	/// the user will employ a non-ambiguous regex for RS.
	/// For example, ab*c is a non-ambiguous regex,
	/// but ab?c?b is an ambiguous regex because
	/// it can match ab or abc, and the reader may
	/// accept either one, depending on input buffer boundaries.
	/// The implemented way to employ greedy regex matching
	/// is to consume subsequent input until the match
	/// does not occur at the end of the input buffer,
	/// or no input is available.  However, this behavior
	/// is not desireable in all cases (i.e., interactive
	/// input against some sort of ambiguous newline
	/// regex).  To enable greedy RS regex consumption,
	/// use <code>-Djawk.forceGreedyRS=true</code>.
	/// </remarks>
	public class PartitioningReader : System.IO.StreamReader
	{
		private static readonly bool FORCE_GREEDY_RS;

		static PartitioningReader()
		{
            string grs = ConfigurationManager.AppSettings["jawk.forceGreedyRS"];
            bool.TryParse(grs, out FORCE_GREEDY_RS);
		}

		private Regex rs;

		
		private bool from_filename_list;

		/// <summary>Construct the partitioning reader.</summary>
		/// <remarks>Construct the partitioning reader.</remarks>
		/// <param name="r">The reader containing the input data stream.</param>
		/// <param name="rs_string">The record separator, as a regular expression.</param>
        public PartitioningReader(StreamReader r, string rs_string) : this(r.BaseStream, rs_string , false)
		{
		}

		/// <summary>Construct the partitioning reader.</summary>
		/// <remarks>Construct the partitioning reader.</remarks>
		/// <param name="r">The reader containing the input data stream.</param>
		/// <param name="rs_string">The record separator, as a regular expression.</param>
		/// <param name="from_filename_list">
		/// Whether the underlying input reader
		/// is a file from the filename list (the parameters passed
		/// into AWK after the script argument).
		/// </param>
		public PartitioningReader(Stream r, string rs_string, bool from_filename_list
			) : base(r)
		{
			this.from_filename_list = from_filename_list;
			RS(rs_string);
		}

		private string prior_rs_string = null;

		private bool consume_all = false;

		/// <summary>Assign a new record separator for this partitioning reader.</summary>
		/// <remarks>Assign a new record separator for this partitioning reader.</remarks>
		/// <param name="rs_string">The new record separator, as a regular expression.</param>
		public virtual void RS(string rs_string)
		{
			//assert !rs_string.equals("") : "rs_string cannot be BLANK";
			if (!rs_string.Equals(prior_rs_string))
			{
				if (rs_string.Equals(string.Empty))
				{
					consume_all = true;
					rs = new Regex("\\z", RegexOptions.Multiline | RegexOptions.Singleline);
				}
				else
				{
					consume_all = false;
                    rs = new Regex(rs_string, RegexOptions.Multiline | RegexOptions.Singleline);
				}
				prior_rs_string = rs_string;
			}
		}

		/// <returns>
		/// true whether the underlying input reader is from a
		/// filename list argument; false otherwise
		/// </returns>
		public virtual bool FromFilenameList()
		{
			return from_filename_list;
		}

		private System.Text.StringBuilder remaining = new System.Text.StringBuilder();

		private char[] read_buf = new char[4096];

		/// <exception cref="System.IO.IOException"></exception>
		public override int Read(char[] b, int start, int len)
		{
			int ret_val = base.Read(b, start, len);
			if (ret_val >= 0)
			{
				remaining.Append(b, start, ret_val);
			}
			return ret_val;
		}

		public virtual bool WillBlock()
		{
            return (consume_all || eof || remaining.Length == 0 || !rs.IsMatch(remaining.ToString()));
		}

		private bool eof = false;

		/// <summary>Consume one record from the reader.</summary>
		/// <remarks>
		/// Consume one record from the reader.  It uses the record
		/// separator regular expression to mark start/end of records.
		/// </remarks>
		/// <returns>the next record, null if no more records exist</returns>
		/// <exception cref="System.IO.IOException">upon an IO error</exception>
		public virtual string ReadRecord()
		{
			
            while (consume_all || eof || remaining.Length == 0 || !rs.IsMatch(remaining.ToString()))
			{
				int len;
				if (eof || (len = Read(read_buf, 0, read_buf.Length)) == 0)
				{
					eof = true;
					string local_ret_val = remaining.ToString();
					remaining.Length = 0;
                    if (local_ret_val.Length == 0)
					{
						return null;
					}
					else
					{
                        return local_ret_val;
					}
				}
				else
				{
					//throw new System.Exception("len == 0 ?!");
				}
			}
			
			// if force greedy regex consumption:
			if (FORCE_GREEDY_RS)
			{
				// attempt to move last match away from the end of the input
				// so that buffer bounderies landing in the middle of
				// regexp matches that *could* match the regexp if more chars
				// were read
				// (one char at a time!)
                
                int end = rs.Match(remaining.ToString()).Index + rs.Match(remaining.ToString()).Length;
                while (rs.IsMatch(remaining.ToString()) && end == remaining.Length)
				{
					if (Read(read_buf, 0, 1) >= 0)
					{
                        end = rs.Match(remaining.ToString()).Index + rs.Match(remaining.ToString()).Length;
						//matcher = rs.Matcher(remaining);
					}
					else
					{

						break;
					}
				}
			}
			// we have a record separator!
			string[] split_string = rs.Split(remaining.ToString(), 2);
			string ret_val = split_string[0];
			remaining.Length = 0;
			// append to remaining only if the split
			// resulted in multiple parts
			if (split_string.Length > 2)
			{
				remaining.Append(split_string[2]);
			}
			return ret_val;
		}
	}
}
